package com.woniuxy.filter;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.woniuxy.commons.utils.JWTUtil;
import com.woniuxy.commons.utils.ResStatus;
import com.woniuxy.commons.utils.ResponseResult;
import com.woniuxy.commons.utils.TokenEnum;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 自定义过滤器实现根据uri判断是否需要认证、token是否合法
 */

@Component
public class AuthFilter implements GlobalFilter, Ordered {
    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    // 执行过滤的操作：写具体的业务
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取uri
        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getURI().getPath();
        System.out.println("当前访问的uri："+uri);

        // 获取response对象
        ServerHttpResponse response = exchange.getResponse();

        //2.判断该uri是否需要认证
        if (requireAuth(uri)){
            //3.需要认证
            System.out.println(uri+"，需要认证");

            //4.去请求头中获取token
            // 如果请求头中没有Authorization，获取到的list的值为null
            List<String> tokens = request.getHeaders().get("Authorization");
            if (tokens != null){
                String token = tokens.get(0);

                //5.校验token
                TokenEnum result = JWTUtil.verify(token);
                if (result == TokenEnum.TOKEN_EXPIRE){
                    System.out.println("token已过期");
                    // 过期
                    // 获取refreshToken(键)
                    String refreshToken = request.getHeaders().get("refreshToken").get(0);
                    System.out.println("refreshToken：" + refreshToken);

                    // 获取token(值)
                    Map<Object, Object> entries = redisTemplate.opsForHash().entries(refreshToken);

                    //判断refreshToken
                    if (!redisTemplate.hasKey(refreshToken)){
                        System.out.println("refreshToken过期");
                        // 去登录
                        return goLogin(response);
                    }

                    //从原来的token中获取用户信息，并通过用户信息重新生成token
                    token = JWTUtil.generateToken(JWTUtil.getUsername(token));

                    //更新一下redis中的token
                    entries.put("token",token);

                    //重新将entries放回redis
                    redisTemplate.opsForHash().putAll(refreshToken,entries);

                    // 重新设置过期时间
                    redisTemplate.expire(refreshToken,60, TimeUnit.SECONDS);

                    // 将token返回给前端
                    response.getHeaders().add("Authorization",token);
                    response.getHeaders().add("Access-Control-Expose-Headers","Authorization");

                    // 将新的token转发给微服务
                    // request.getHeaders().set("Authorization",token);

                    ServerHttpRequest.Builder requestBuilder = request.mutate();
                    // 先删除，后新增
                    requestBuilder.headers(k -> k.remove("Authorization"));
                    requestBuilder.header("Authorization", token);
                    ServerHttpRequest requestNew = requestBuilder.build();
                    exchange.mutate().request(requestNew).build();
                    return chain.filter(exchange);
                }else if (result == TokenEnum.TOKEN_BAD){
                    // 无效的token
                    System.out.println("token无效");
                    return goLogin(response);
                }
            }else {
                // 没token，返回结果叫别人去登录
                System.out.println("token为空");
                return goLogin(response);
            }
        }
        System.out.println("当前url放行");
        return chain.filter(exchange);  // 放行
    }

    // 返回结果：叫用户登录
    private Mono<Void> goLogin(ServerHttpResponse response) {
        // 设置响应头
        response.getHeaders().add("Content-Type","application/json;charset=utf-8");

        // 返回的数据
        ResponseResult<Object> responseResult = new ResponseResult<>();
        responseResult.setCode(401);
        responseResult.setStatus(ResStatus.NO_LOGIN);
        responseResult.setMsg("请登录 gateway");

        try {
            // 将对象转换成JSON
            String data = new ObjectMapper().writeValueAsString(responseResult);

            // 将数据返回给浏览器、并停止当前请求
            DataBuffer buffer = response.bufferFactory().wrap(data.getBytes());

            return response.writeWith(Mono.just(buffer));
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    // 判断uri是否需要认证
    private boolean requireAuth(String uri){
        // 罗列所有不需要认证的uri：uri别写死，应该在配置文件、数据库中去指定
        // 假设加载到了uri信息
        List<String> no_auth_uri = Arrays.asList("/login/");

        // 遍历判断当前uri是否需要认证
        for(String nau : no_auth_uri){
            if (uri.startsWith(nau)){
                return false;
            }
        }

        return true;
    }


    @Override
    public int getOrder() {
        return 0;
    }
}
